/*
 * 代号：凤凰
 * http://www.jphenix.org
 * 2014年6月21日
 * V4.0
 */
package com.jphenix.kernel.script;

import java.io.File;
import java.io.IOException;
import java.io.PrintStream;
import java.net.URL;
import java.net.URLDecoder;
import java.util.ArrayList;
import java.util.List;

import javax.tools.Diagnostic;
import javax.tools.DiagnosticCollector;
import javax.tools.JavaCompiler;
import javax.tools.JavaFileObject;
import javax.tools.ToolProvider;

import com.jphenix.kernel.baseobject.instanceb.ABase;
import com.jphenix.kernel.objectloader.vo.BeanVO;
import com.jphenix.share.lang.SDate;
import com.jphenix.share.lang.SString;
import com.jphenix.share.printstream.PrintStreamTool;
import com.jphenix.share.printstream.StringPrintStreamTool;
import com.jphenix.share.tools.Base64;
import com.jphenix.share.tools.FileCopyTools;
import com.jphenix.share.tools.SysUtil;
import com.jphenix.share.util.BaseUtil;
import com.jphenix.share.util.ClassUtil;
import com.jphenix.share.util.DebugUtil;
import com.jphenix.share.util.SFilesUtil;
import com.jphenix.share.util.StringUtil;
import com.jphenix.standard.docs.ClassInfo;
import com.jphenix.standard.exceptions.MsgException;
import com.jphenix.standard.lang.IPrintStream;
import com.jphenix.standard.script.IScriptLoader;

/**
 * 脚本编译器
 * 
 * 注意：编译后的Java类开头必须是字母，因为类名不能以数字开头，已经做过去掉开头字母的尝试，失败。
 * 
 *       以前拼参数变量都是这么拼的  <@key>  拼SQL语句中的参数变量都是这么拼的 <@key@> 现在都是 <@key@>
 * 
 * 2018-07-05 增加了 <%JS  JS%> 关键字，用来直接编写js内容，输出到页面
 * 2018-07-23 修改了统计脚本最快执行时间永远为0的错误
 * 2018-09-03 增加了编译警告信息，避免出现 <%000001%> 忘加括号的写法，不容易找到问题
 * 2018-11-13 如果引用的父类不是脚本，会报错，修改之
 * 2018-12-26 在构造脚本时，增加了核心接口类路径引用
 * 2019-01-16 增加了脚本引用扩展库（设定扩展库文件夹）功能
 * 2019-01-18 适配了新的脚本类加载器
 * 2019-01-24 修改了脚本支持扩展包路径后的运行错误。修改了直接调用脚本时发生的错误
 * 2019-01-28 增加了脚本执行报错后，将最后一次错误信息记录到ScriptVO中
 * 2019-04-09 在构造脚本时增加了获取脚本对应的ScriptVO类实例方法
 * 2019-06-13 脚本传参中增加了默认值，是否需要base64解码
 * 2019-07-09 完善了注释
 * 2019-07-20 适应ScriptVO中的绝对路径改为相对路径
 * 2019-09-11 简化了拼装脚本代码，禁止拼装调用其它脚本代码末尾不加;符号
 *            又简化了代码，去掉了自动判断是否带返回值
 * 2019-09-23 支持Java源码不用做Base64保存到脚本XML文件中，这样方便做Git提交比较差异
 *            原本不能直接保存源码是因为：源码中可能存在XML中的结束节点标识，导致脚本XML文件保存错误无法解析。
 *            这次替换了Java源码中的字符串：<![CDATA[ 替换为：<!^^CDATA[，替换了：]]> 替换为：]^^>
 *            在显示源码时或编译之前或显示编译后源码之前再转回来
 * 2019-10-22 增加了调用脚本方法，新的方法中增加了是否需要返回值参数，用于调用远程脚本
 * 2019-10-23 修改了测试时发现的问题
 * 2019-11-01 修改了设置脚本错误信息的方法（以前是直接设置变量值，现在改用通过方法设置错误信息）
 * 2019-11-04 在构建classPath编译类路径时，增加了解析扩展库文件夹中readme.txt中引用其它扩展库路径信息
 * 2019-11-06 支持在复杂语句段<%S S%>中，支持简单方法插入语句段变量 <@*sqlSubVar@>
 * 2019-11-12 支持将编译前的Java文件 另存到指定文件夹 （通常用于提交到Git，通过PipeLine做代码质量检查）
 *            另存路径配置节点名：script_java_source_base_path
 *            去掉了编译前Java文件源码中的冗余注释
 *            去掉了另存出来的java源文件开头的前缀，这些文件仅供检阅使用
 * 2019-11-19 将另存出来的编译前的Java文件的文件名与编译后的文件保持一致，便于校验编译结果检测
 * 2019-11-20 在脚本中不直接将异常打印到控制台，而是通过DebugUtil.pe(e)输出到控制台，避免SonarQube认为这是错误
 * 2019-11-26 在编译前的Java文件文件中增加了当前开发人员信息，程序创建时间，更新次数等信息
 * 2019-11-29 修改了传统类中包含了<![CDATA[值时，处理错误的问题
 * 2019-12-06 在脚本中支持另写两个方法，一个是在执行中发生异常时调用的方法doException。一个是无论执行正常结束，异常结束时都调用的方法doFinally
 * 2019-12-09 修改了增加执行异常处理方法时，忽略了返回值的错误。
 *            为doException和doFinally增加了传入参数
 * 2019-12-11 修改了删除编译前源文件的逻辑
 * 2019-12-12 补充了编译前的java文件中的注释，增加了doException方法允许返回值
 * 2020-04-03 在脚本中增加了与JDK1.8冲突的Base64引用路径
 * 2020-05-13 增加了 <%(NormalCls.class)%> 和 <%&(NormalCls.class)%> 写法，用来获取传统类的类实例，并且支持修改这个传统类实例后，引用这个类的脚本都自动重新加载。
 * 2020-06-16 去掉了构造脚本时，检测引用的脚本是否存在。因为引用的脚本可能是通过路由功能调用的远程脚本。
 * 2020-08-08 屏蔽掉了无用的警告日志
 * 2020-09-09 在构造脚本类时，增加了可以将传入参数变量设置为类变量模式
 * 2020-09-15 有时候写动作脚本，一不小心给该脚本设置了返回值，导致动作处理类错误的调用了带返回值的空方法，无法找出原因。所以在空方法中增加了日志提醒。
 * 2020-09-25 在代码中调用其它脚本传参，参数名与变量名相同时 {!key} 可以简化成 {key}
 * 2021-05-10 解决了预览代码与编译代码行号不一致的问题，查看报错信息时，在预览代码中定位不准确
 * 2023-04-03 去掉了扩展库管理功能，仅保留了扩展库主键。原本扩展库支持指定脚本加载指定的jar包（其他脚本可以不加载）
 *            但这样做过于复杂，执行效率也低。原本这么设计是为了在项目中，不同脚本可以引用相同功能不同版本号的jar包
 *            但这种情况可以变通处理（只保留最高版本），而且这种情况几乎没有。
 * 2024-03-27 适配了JDK17
 * 2024-06-05 取消扩展库功能，改用：在WEB-INF/lib中创建子文件夹将jar包归类
 * 2024-06-05 修改了创建文件时调用的方法，绝对路径使用SFilesUtil
 * 2024-06-12 解决了多次编译，错误信息重复的问题
 * 2024-07-03 修改了因为BaseUtil.trim方法优化后暴露出来的错误
 * 2024-07-25 取消了是否禁用并发控制（太消耗系统资源，已取消）
 * 2024-08-08 脚本中，待返回值和不带返回值改为共用一个方法，不再区分出两个方法
 * 
 * @author 马宝刚
 * 2014年6月21日
 */
@ClassInfo({"2024-08-08 19:19","脚本编译器"})
public class ScriptCompiler extends ABase {

  private String       baseClassPath = null; // 基础类路径
  private ScriptLoader sl            = null; // 脚本类加载器
  private JavaCompiler compiler      = null; // 编译器

  /**
   * 构造函数
   * 
   * @author 马宝刚
   */
  public ScriptCompiler(ScriptLoader sl) {
    super();
    this.sl = sl;
    setBase(sl);
    
   	compiler = ToolProvider.getSystemJavaCompiler();
    if(compiler==null) {
        sl.log("当前版本的JDK不支持动态编译，即ToolProvider.getSystemJavaCompiler()为空");
    }
    sl.fm = new ScriptFileManager(sl,compiler.getStandardFileManager(new DiagnosticCollector<JavaFileObject>(), null, null));
  }


  /**
   * 编译脚本文件
   * 
   * @param sVO 脚本信息类
   * @param cl  脚本类加载器 2014年7月23日
   * @author 马宝刚
   */
  public void compile(ScriptVO sVO) {
    // 构建java源文件
    buildSource(sVO);
    if (sVO.hasError()) {
      return;
    }
    // 编译源文件
    buildClass(sVO);
  }

  /**
   * 建立Class文件
   * 
   * @param sVO 脚本信息类 2014年7月24日
   * @author 马宝刚
   */
  protected void buildIndependentClass(ScriptVO sVO) {
	//#region 取消扩展库功能，改用：/WEB-INF/lib 中创建子文件夹将jar包归类
	/*
	if(sVO.extLibErrorMsg!=null && sVO.extLibErrorMsg.length()>0) {
	  return;
	}
    // 从类源码中获取扩展类路径
    if (sVO.sourceContent != null) {
      // 解码代码
      String content = d64(sVO.sourceContent);
      // 获取是否存在扩展库路径信息
      int point = content.indexOf("@ExtLib({");
      if (point > -1) {
        point += 9;
        // 获取扩展库路径信息结束符
        int endPoint = content.indexOf("})", point);
        if (endPoint > 0) {
          String libPathStr = content.substring(point, endPoint);
          libPathStr = BaseUtil.trim(libPathStr, " ", "\t", "\"");
          libPathStr = BaseUtil.swap(libPathStr, " ", "", "\t", "");
          libPathStr = BaseUtil.swap(libPathStr, "\",\"", ";");
          sVO.setExtLibs(BaseUtil.split(libPathStr, ";", "；", ":", "：", ",", "，"));

        }
      }
    }
    */
    //#endregion 
    
    // 执行编译，在java源文件同级文件夹
    sVO.addErrorMsg(compile(sl.getClassBasePath() + sVO.buildSourceFilePath, sVO));
    if (sVO.hasError()) {
      log.warning(sVO.errorMsg, null);
      return;
    }
    // 重新加载路径信息
    sl.fm.reload(sVO);

    // 编译前的java源文件
    String javaSourcePath = sl.getClassBasePath() + sVO.buildSourceFilePath;
    boolean delSource = sl.deleteBuildSource;
    // 是否需要将编译前的Java文件另存到指定文件夹
    String saveJavaSourcePath = p("script_java_source_base_path");
    if (saveJavaSourcePath.length() > 0) {
      delSource = false; // 如果需要另存源文件，则不删除
      saveJavaSourcePath = sourcePath(saveJavaSourcePath);
      try {
        FileCopyTools.copy(javaSourcePath,
            SFilesUtil.getFilePath(saveJavaSourcePath + sVO.buildSourceFilePath) + "/" + sVO.className + ".java");
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    if (delSource) {
      (new File(javaSourcePath)).delete();
    }
    String[] clsInfos = null; // 类信息
    try {
      // 获取传统类的类信息
      clsInfos = ClassUtil.getClassInfo(sl.fm.getClassFile(sVO));

      sVO.sourceFileVer = clsInfos[0];
      sVO.classFileVer = clsInfos[0];
    } catch (Exception e) {
      e.printStackTrace();
    }
    sVO.sourceFileStatus = 0;
    sVO.updateClassFileTime(); // 更新编译后的时间
    sVO.updateSourceLastChanged(); // 更新源文件最后修改时间

    // 重新加载
    sl.fm.reload(sVO);

    // 内置脚本不要回写源文件
    if (!sVO.independentClassAfterSave && !sVO.nativeScript) {
      // 回写文件版本号
      sl.writeSourceFile(sVO);
    }
  }

  /**
   * 建立Class文件
   * 
   * @param sVO 脚本信息类 2014年7月24日
   * @author 马宝刚
   */
  protected void buildClass(ScriptVO sVO) {
	  
	/*
	取消扩展库功能，改用：在WEB-INF/lib中创建子文件夹将jar包归类
	if(sVO.extLibErrorMsg!=null && sVO.extLibErrorMsg.length()>0) {
	  return;
	}
	*/
	  
    // 需要移除现有类的信息，避免重新编译时，
    // 直接加载了老的子类，而没有重新编译关联的内部子类
    sl.rebuildClassLoader(sVO);

    sl.fm.delete(sVO);

    if (sVO.independentClass) {
      buildIndependentClass(sVO);
      return;
    }
    // 执行编译，在java源文件同级文件夹
    if(SysUtil.getSystemType()==SysUtil.TYPE_SYSTEM_WINDOWS) {
    	// 如果是windows系统，路径严格分割符为 \\ 不能是/ 虽然大部分时候/都好使，但关键时候就是不好使，必须是 D:\\aaa\\bbb.txt
    	String path = BaseUtil.swap(sl.getClassBasePath() + sVO.buildSourceFilePath,"/","\\\\");
    	// 如果路径是  \\D:\\aaa 改成  D:\\aaa
    	if(path.startsWith("\\\\")) {
    		path = path.substring(2);
    	}
    	sVO.setErrorMsg(compile(path, sVO));
    }else {
    	sVO.setErrorMsg(compile(sl.getClassBasePath() + sVO.buildSourceFilePath, sVO));
    }
    if (sVO.hasError()) {
      log.warning(sVO.errorMsg, null);
      return;
    }
    // 编译前的java源文件
    String javaSourcePath = sl.getClassBasePath() + sVO.buildSourceFilePath;
    // 是否在编译脚本类后，删除源文件
    boolean delSource = sl.deleteBuildSource;
    // 是否需要将编译前的Java文件另存到指定文件夹
    String saveJavaSourcePath = p("script_java_source_base_path");
    if (saveJavaSourcePath.length() > 0) {
      delSource = false; // 如果需要另存源文件，则不删除
      saveJavaSourcePath = sourcePath(saveJavaSourcePath);
      try {
        FileCopyTools.copy(javaSourcePath,
            SFilesUtil.getFilePath(saveJavaSourcePath + sVO.buildSourceFilePath) + "/" + sVO.className + ".java");
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
    if (delSource) {
      (new File(javaSourcePath)).delete();
    }
    sVO.classFileVer = sVO.sourceFileVer;
    sVO.sourceFileStatus = 0;
    sVO.updateClassFileTime(); // 更新编译后的时间
    sVO.updateSourceLastChanged(); // 更新源文件最后修改时间

    // 重新加载新的信息
    sl.fm.reload(sVO);
  }

  /**
   * 返回编译源码
   * 
   * @param sVO 脚本信息类
   * @return 编译源码 2014年8月15日
   * @author 马宝刚
   */
  public String outSource(ScriptVO sVO) {
    // 构建返回值
    StringPrintStreamTool spst = new StringPrintStreamTool();
    try {
      if (sVO.independentClass) {
        outIndependentSource(sVO, spst);
      } else {
        outSource(sVO, spst); // 输出编译源码
      }
    } catch (Exception e) {
      e.printStackTrace();
      sVO.addErrorMsg("Out Source Exeption:[" + DebugUtil.getExceptionInfo(e, "\n") + "]");
    }
    return spst.toString();
  }

  /**
   * 输出独立的java类远吗
   * 
   * @param sVO 脚本信息类
   * @param out 输出流对象
   * @throws Exception 异常 2016年4月8日
   * @author 马宝刚
   */
  protected void outIndependentSource(ScriptVO sVO, IPrintStream out) throws Exception {

    String sourceCode = sVO.sourceContent; // 源码
    if (sourceCode == null) {
      throw new MsgException(this, "类中不能为空");
    }
    // 如果在页面中提交脚本内容时，采用了base64加密，在这里要对内容解密
    if (sourceCode.startsWith("@b64@")) {
      sourceCode = sourceCode.substring(5);
      sourceCode = Base64.base64Decode(sourceCode, "UTF-8");
    } else {
      sourceCode = BaseUtil.swap(sourceCode, "<!^^CDATA[", "<![CDATA[", "]^^>", "]]>");
    }
    if (sourceCode.indexOf("package ") > -1) {
      throw new MsgException(this, "在脚本中编写传统类，不必写包路径，包路径会统一为：" + IScriptLoader.SCRIPT_PACKAGE);
    }
    out.println("/*");
    out.println(" * " + IScriptLoader.CLASS_SOURCE_HREF);
    out.println(" * " + SDate.nowDateTimeString());
    out.println(" * " + IScriptLoader.CLASS_SOURCE_FILE_VER);
    out.println(" */");
    out.println("package " + IScriptLoader.SCRIPT_PACKAGE + ";");
    out.println();

    out.println("/**");
    out.println(" * " + sVO.title);

    out.println(" * " + SDate.nowDateTimeString());
    out.println(" */\n\n");

    out.println(sourceCode);
  }

  /**
   * 输出编译源码
   * 
   * @param sVO 脚本信息类
   * @param out 输出流对象
   * @throws Exception 异常 2014年8月15日
   * @author 马宝刚
   */
  protected void outSource(ScriptVO sVO, IPrintStream out) throws Exception {
    // 导入类信息序列
    List<String> importList = new ArrayList<String>();
    // 解析脚本文件
    StringBuffer[] contents = parseSource(sVO, importList);
    out.println("/*");
    out.println(" * " + IScriptLoader.CLASS_SOURCE_HREF);
    out.println(" * " + SDate.nowDateTimeString());
    out.println(" * " + IScriptLoader.CLASS_SOURCE_FILE_VER);
    out.println(" */");
    out.println("package " + IScriptLoader.SCRIPT_PACKAGE + ";");
    out.println();
    // 输出导入信息
    for (String importStr : importList) {
      out.println(importStr);
    }
    out.println();

    out.println("/**");
    out.println(" * " + sVO.title);
    // 供应商信息
    if (sVO.devName != null && sVO.devName.length() > 0) {
      out.println(" * @development " + sVO.devName + "(" + sVO.devCode + ")");
    }else{
      //占位，避免预览源码与编译源码行号不一致
      out.println(" * @development");
    }
    // 开发人信息
    if (sVO.uMan != null && sVO.uMan.length() > 0) {
      out.println(" * @author " + sVO.uMan + "(" + sVO.uUser + ")");
    }else{
      //占位，避免预览源码与编译源码行号不一致
      out.println(" * @author");
    }
    // 首次创建时间
    if (sVO.createTime != null && sVO.createTime.length() > 0) {
      out.println(" * @FirstCreateTime " + sVO.createTime);
    }else{
      //占位，避免预览源码与编译源码行号不一致
      out.println(" * @FirstCreateTime");
    }
    // 更新次数
    if (sVO.uCount != null && sVO.uCount.length() > 0) {
      out.println(" * @UpadteCount " + sVO.uCount);
    }else{
      //占位，避免预览源码与编译源码行号不一致
      out.println(" * @UpadteCount");
    }
    // 上次修改内容
    if (sVO.modifyContent != null && sVO.modifyContent.length() > 0) {
      out.println(" * @ModifyContent " + sVO.modifyContent);
    }else{
      //占位，避免预览源码与编译源码行号不一致
      out.println(" * @ModifyContent");
    }
    out.println(" * " + SDate.nowDateTimeString());
    out.println(" */");

    // 在此放入脚本加载器的版本号，如果发现编译后的脚本中的这个值不等于当前
    // 脚本加载器中的版本号值，会重新解析编译这个脚本
    // 通常用在升级框架，需要重新编译已经运行的脚本
    out.println(sVO.buildScriptInfo());
    out.println("@ClassInfo({\"" + sVO.sourceFileVer + "\",\"" + sVO.title + "\"})");
    out.println("@Running({\"" + sVO.singletonLevel + "\",\"" + SString.valueOf(sVO.afterStartMethodName) + "\",\""
        + SString.valueOf(sVO.initMethodName) + "\",\"" + SString.valueOf(sVO.destoryMethodName) + "\"})");
    if (sVO.registerID != null && sVO.registerID.length() > 0) {
      out.println("@Register({\"" + sVO.registerID + "\"})");
    }
    if (sVO.isShadow) {
      out.println("@Shadow({\"1\"})");
    }
    String pClass = null; // 父类路径
    String pBeanID = sVO.parentBeanID; // 父类主键
    boolean isScriptParent = false; // 父类是否为脚本
    if (pBeanID == null || pBeanID.length() < 1) {
      pClass = "com.jphenix.kernel.baseobject.instanceb.ABase";
    } else {
      if (pBeanID.indexOf(".") > 0) {
        /*
         * 支持在父类主键处直接写完整的类路径
         */
        pClass = pBeanID;
      } else {
        // 父类容器
        BeanVO pBeanVO = getBeanFactory().getBeanVO(pBeanID, this);
        if (pBeanVO != null) {
          pClass = pBeanVO.classPath;
        } else {
          // 尝试获取父类脚本，在这里要使用内部方法，直接拿到原始的
          // VO，然后对该vo做初始化
          ScriptVO pSVO = sl.getScriptInfo(pBeanID, false);
          if (pSVO != null) {
            if (!pSVO.independentClass) {
              // 并且父类并不是传统类脚本
              isScriptParent = true;
            }
            pClass = pSVO.classPath;
            if (pClass == null) {
              // 父类脚本还没被初始化，这种情况只有在启动时
              // 初始化脚本时才会遇到
              sl.initScript(pSVO, 0);
              pClass = pSVO.classPath;
            }
          } else if (_beanFactory.beanExists(pBeanID)) {
            // 传统类父类

            // 传统父类信息容器
            BeanVO bVO = _beanFactory.getBeanVO(pBeanID, this);
            pClass = bVO.classPath;
          } else {
            // 错误信息
            String errorMsg = "ScriptCompiler Not Find The Parent Class BeanID:[" + pBeanID + "]";
            log.error(errorMsg, null);
            throw new MsgException(this, errorMsg);
          }
        }
      }
    }
    String pClassName; // 父类名
    if (pClass != null && pClass.length() > 0) {
      int point = pClass.lastIndexOf(".");
      if (point > 0) {
        pClassName = pClass.substring(point + 1);
      } else {
        pClassName = pClass;
      }
    } else {
      pClassName = "";
    }
    String resCls = sVO.outType; // 返回值类型
    if (resCls == null || resCls.length() < 1) {
      resCls = "void";
    }
    if (sVO.isParent) {
      // 如果是父类，标记父类，和父类说明
      out.println("@BeanInfo({\"" + sVO.id + "\",\"true\",\"" + sVO.parentBeanID + "\",\"" + sVO.title + "\",\"0\",\""
          + pClassName + "\"})");
    } else {
      out.println(
          "@BeanInfo({\"" + sVO.id + "\",\"false\",\"" + sVO.parentBeanID + "\",\"\",\"0\",\"" + pClassName + "\"})");
    }
    if (sVO.interfaceClasspath != null && sVO.interfaceClasspath.length() > 0) {
      // 实现了多接口，通常情况下，将脚本直接当作java类使用时会用到
      out.println("public class " + sVO.className + " extends " + pClass + " implements IScriptBean,"
          + sVO.interfaceClasspath + " {");
    } else {
      out.println("public class " + sVO.className + " extends " + pClass + " implements IScriptBean {");
    }
    if (!isScriptParent) {
      // 设置参数变量 如果父类是脚本，那么这个变量在父类中就已经声明好了
      out.println();
      out.println("	protected IScriptLoader _scriptLoader = null;	//ScriptLoader");
      out.println("	protected ScriptVO      _scriptVO     = null;	//ScriptVO");
    }
    if (sVO.isClassVar && sVO.inFields != null) {
      String fieldVal;
      for (ScriptFieldVO fvo : sVO.inFields) {
        if ("int".equals(fvo.type) || "float".equals(fvo.type) || "double".equals(fvo.type)
            || "long".equals(fvo.type)) {
          fieldVal = "0";
        } else if ("boolean".equals(fvo.type)) {
          fieldVal = "false";
        } else {
          fieldVal = "null";
        }

        out.println("	private " + fvo.type + " " + fvo.name + " = " + fieldVal + ";	//"
            + BaseUtil.swap(fvo.explain, "\n", " "));
      }
    }
    // 设置构造函数
    out.println();
    out.println("   /**");
    out.println("    * 构造函数");
    out.println("    * @param sl 脚本加载器");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    */");
    if (isScriptParent) {
      out.println("   public " + sVO.className + "(IScriptLoader sl,ScriptVO sVO) {\n        super(sl,sVO);\n   }");
    } else {
      out.println("   public " + sVO.className
          + "(IScriptLoader sl,ScriptVO sVO) {\n        super();\n        _scriptLoader = sl;\n        _scriptVO = sVO;\n   }");
    }

    out.println(contents[0]); // 输出类内部其他非过程模式代码

    /*
     * 构造获取当前脚本主键方法
     */
    out.println();
    out.println("   /**");
    out.println("    * 获取当前脚本主键");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 脚本主键");
    out.println("    */");
    out.println("   public String getScriptId() {");
    out.println("      return \"" + str(sVO.id) + "\";");
    out.println("   }");

    /*
     * 构造获取当前脚本的ScriptVO类实例方法
     */
    out.println();
    out.println("   /**");
    out.println("    * 获取当前脚本的ScriptVO类实例方法");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 当前脚本的ScriptVO类实例方法");
    out.println("    */");
    out.println("   public ScriptVO getScriptVO() {");
    out.println("      return _scriptVO;");
    out.println("   }");

    /*
     * 构造获取当前脚本声明的变量是否都是类变量
     */
    out.println();
    out.println("   /**");
    out.println("    * 获取当前脚本的ScriptVO类实例方法");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 当前脚本的ScriptVO类实例方法");
    out.println("    */");
    out.println("   public boolean isClassVar() {");
    out.println("      return " + (sVO.isClassVar ? "true" : "false") + ";");
    out.println("   }");

    /*
     * 构造当前脚本是否输出日志方法
     */
    out.println();
    out.println("   /**");
    out.println("    * 当前脚本是否输出日志");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 是否输出日志");
    out.println("    */");
    out.println("   public boolean noOutLog() {");
    out.println("      return " + (sVO.noOutLog ? "true" : "false") + ";");
    out.println("   }");

    /*
     * 构造当前脚本是否启用数据库事务处理
     */
    out.println();
    out.println("   /**");
    out.println("    * 当前脚本是否启用数据库事务处理");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 是否启用数据库事务处理");
    out.println("    */");
    out.println("   public boolean isDbTransaction() {");
    out.println("       return " + (sVO.isDbTransaction ? "true" : "false") + ";");
    out.println("   }");

    /*
     * 构造获取当前脚本加载器方法
     */
    out.println();
    out.println("   /**");
    out.println("    * 获取当前脚本加载器");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 脚本加载器");
    out.println("    */");
    out.println("   public IScriptLoader getScriptLoader() {");
    out.println("       return _scriptLoader;");
    out.println("   }");

    /*
     * 构造获取当前脚本被调用脚本主键序列
     */
    out.println();
    out.println("   /**");
    out.println("    * 获取当前脚本的被调用脚本主键序列 ");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 脚本主键序列");
    out.println("    */");
    out.println("   public List<String> getForScriptIds() {");
    out.println("      List<ScriptVO> sList = _scriptLoader.getForInclude(\"" + str(sVO.id) + "\");");
    out.println("      List<String> reList = new ArrayList<String>();");
    out.println("      for(ScriptVO ele:sList){");
    out.println("         reList.add(ele.id);");
    out.println("      }");
    out.println("      return reList;");
    out.println("   }");

    /*
     * 构造传入参数序列
     */
    out.println();
    out.println("   /**");
    out.println("    * 获取传入参数类型序列");
    out.println("    * " + SDate.nowDateTimeString());
    out.println("    * @return 传入参数类型序列");
    out.println("    */");
    out.println("   public List<ScriptFieldVO> getParameterList() {");
    out.println("      ArrayList<ScriptFieldVO> reList = new ArrayList<ScriptFieldVO>();");
    for (ScriptFieldVO sfVO : sVO.inFields) {
      out.println("      reList.add(new ScriptFieldVO(\"" + sfVO.name + "\",\"" + sfVO.type + "\","
          + (sfVO.defValue == null ? "null" : "\"" + sfVO.defValue + "\"") + ",\""
          + BaseUtil.swapString(sfVO.explain, "\r", "", "\n", "", "\"", "\\\"") + "\"," + Boolean.valueOf(sfVO.notNull)
          + "," + Boolean.valueOf(sfVO.isStaticPara) + "," + Boolean.valueOf(sfVO.needB64Dec) + "));");
    }
    out.println("      return reList;");
    out.println("   }");

    if (!sVO.isParent) {
      /*
       * 注意：父类没有默认脚本执行方法 _execute
       */

      // 构造执行方法
      out.println();
      out.println("   /**");
      out.println("    * 脚本执行入口（空方法）");
      out.println("    * " + SDate.nowDateTimeString());
      out.println("    */");
      // 一下方式实际上就是传说中的JSon传参模式，但是没有使用Json格式
      // 都是内部处理参数，也没必要摆谱，只要能实现目标效果，什么最高效就用什么
      // 实现了什么效果？ 无限、无序传入参数

      // 去掉了脚本isStatic属性，从来没这么用过
      // String staticMark = ""; //是否为静态方法
      // if(sVO.isStatic) {
      // staticMark = "static ";
      // }

      String mainMethodName; // 脚本主方法名

      // 构造执行方法
      out.println();
      out.println("   /**");
      out.print("    * 脚本执行入口");
      if(sVO.isDbTransaction) {
    	  out.println("（支持事务处理）");
      }else {
    	  out.println();
      }
      out.println("    * " + SDate.nowDateTimeString());
      out.println("    * @param _in 传入参数容器");
      out.println("    * @return    返回执行结果（如果该脚本未设置返回值，则返回null）");
      out.println("    */");
      out.print("   public "); // 脚本方法为公共方法
      // 如果需要标记为同步脚本，必须为常驻内存脚本
      if (sVO.isSync && sVO.singletonLevel > 0) {
        out.print("synchronized ");
      }
      out.println("Object _execute(Map<String,?> _in) throws Exception {");
      
      
      if (sVO.isDbTransaction) {

        // 支持数据库事务处理
        mainMethodName = "_executeTransaction";

        // 构建事务会话主键
        out.println("      String _dbTransactionKey = _scriptLoader.getSn();");
        // 从当前线程中获取事务会话主键序列
        out.println("      ScriptUtil.createDbTransaction(_dbTransactionKey,this);");
        out.println("      try{");
        
        if ("void".equals(resCls)) {
            // 调用脚本主体函数
            out.println("         " + mainMethodName + "( _in);");
            // 提交数据事务
            out.println("         ScriptUtil.commitDbTransaction(_dbTransactionKey,this);");
            out.println("         return null;");
        }else {
            // 调用脚本主体函数
        	if("Object".equals(resCls)) {
        		out.println("         " + resCls + " _resObj =   " + mainMethodName + "( _in);");
        	}else {
        		out.println("         " + resCls + " _resObj =   ("+resCls+")" + mainMethodName + "( _in);");
        	}
            // 提交数据事务
            out.println("         ScriptUtil.commitDbTransaction(_dbTransactionKey,this);");
            out.println("         return _resObj;");
        }
        out.println("      }catch(Exception e){");
        out.println("         error(\"XXXX执行发生异常准备回滚XXXX\",e);");
        // 执行回滚
        out.println("         ScriptUtil.rollbackDbTransaction(_dbTransactionKey,this);");
        out.println("         return _in.get(\"_RETURN_VALUE_\");");
        out.println("      }finally{");
        // 执行提交事务（如果主要靠手动提交，回滚，忘了的话，就会导致整个数据操作报错）
        out.println("         ScriptUtil.closeDbTransaction(_dbTransactionKey,this);");
        out.println("      }");
        out.println("   }\n\n");
      } else {
          // 支持数据库事务处理
          mainMethodName = "_executeNormal";
          if ("void".equals(resCls)) {
              // 调用脚本主体函数
              out.println("         " + mainMethodName + "( _in);");
              out.println("         return null;");
          }else {
              // 调用脚本主体函数
          	  if("Object".equals(resCls)) {
        		out.println("         return " + mainMethodName + "( _in);");
        	  }else {
        		out.println("         return ("+resCls+")" + mainMethodName + "( _in);");
        	  }
          }
          out.println("   }\n\n");
      }
      
      // 普通脚本
      out.print("   private ");
      if("void".equals(resCls)) {
    	  out.print("void ");
      }else {
    	  out.print("Object ");
      }
      out.println(mainMethodName + "(Map<String,?> _in) throws Exception {");

      // 声明用于doException和doFinally变量
      out.println("      Object[] _SUB_PATAS = null; //Used for method doException and doFinally");

      // 在线程对象中写入当前脚本主键
      out.println("      ThreadSession.put(\"_current_execute_script_\",\"" + str(sVO.id) + "\");\n");
      out.println("      long _scriptBeforeExecuteTime = System.currentTimeMillis(); //look the var name boy\n");
      out.println("      try{");
      // 写入脚本内容
      out.println(contents[1]);
      out.println("      }catch(Exception se){");
      out.println("         com.jphenix.share.util.DebugUtil.pe(se);");
      out.println("         _scriptLoader.setScriptRunError(getScriptId(),ts(),DebugUtil.getExceptionInfo(se));");
      out.println("         if(this instanceof IScriptBeanExtra){");
      out.println("           try{");
      out.println("             if(((IScriptBeanExtra)this).doException(se,_in,_SUB_PATAS)){");
      if ("void".equals(resCls)) {
    	  out.println("               return;");
      }else {
    	  out.println("               return _in.get(\"_RETURN_VALUE_\");");
      }
      out.println("             }");
      out.println("           }catch(Exception e2){");
      out.println("             e2.printStackTrace();");
      out.println("           }");
      out.println("         }");
      out.println("         throw se;");
      
      out.println("      }finally{");

      out.println("         _scriptVO.lastExecuteTime=System.currentTimeMillis();");
      out.println("         _scriptVO.taskRunCount++;");

      out.println("         _scriptBeforeExecuteTime = System.currentTimeMillis()-_scriptBeforeExecuteTime;");
      out.println(
          "         if(_scriptVO.maxExecuteTime<_scriptBeforeExecuteTime){_scriptVO.maxExecuteTime=_scriptBeforeExecuteTime;};");
      out.println(
          "         if(_scriptBeforeExecuteTime!=0 && (_scriptVO.minExecuteTime==0 || _scriptVO.minExecuteTime>_scriptBeforeExecuteTime)){_scriptVO.minExecuteTime=_scriptBeforeExecuteTime;};");

      out.println("         if(this instanceof IScriptBeanExtra){");
      out.println("           try{");
      out.println("             ((IScriptBeanExtra)this).doFinally(_in,_SUB_PATAS);");
      out.println("           }catch(Exception e){");
      out.println("             e.printStackTrace();");
      out.println("           }");
      out.println("         }");

      out.println("      }");
      out.println("   }");
    }
    out.println("}");
  }

  /**
   * 建立传统类脚本Java源文件
   * 
   * @param sVO 脚本信息类
   * @return java源文件路径 2014年7月24日
   * @author 马宝刚
   */
  protected void buildIndependentSource(ScriptVO sVO) {
    // java文件对象
    File sourceFile = null;
    // 先删除
    try {
      // 源文件对象
      File file = new File(sl.getClassBasePath() + sVO.buildSourceFilePath);
      if (file.isFile()) {
        // 删除，原本在这里用的是filesUtil中的删除功能，但万一这是一个路径
        // 会把整个路径下的全部文件都删除
        file.delete();
      }
    } catch (Exception e) {
    }
    sourceFile = SFilesUtil.createFile(sl.getClassBasePath() + sVO.buildSourceFilePath);

    // 写入流
    PrintStream out = null;
    try {
      out = new PrintStream(sourceFile, "UTF-8");
      outIndependentSource(sVO, new PrintStreamTool(out)); // 输出内容
    } catch (Exception e) {
      e.printStackTrace();
      sVO.addErrorMsg("The Tradition StringID:[" + sVO.id + "] Create Build Java File:[" + sl.getClassBasePath()
          + sVO.buildSourceFilePath + "] Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
      log.error("The Tradition StringID:[" + sVO.id + "]  Create Build Java File:[" + sl.getClassBasePath()
          + sVO.buildSourceFilePath + "] Exception", e);
      return;
    } finally {
      out.flush();
      out.close();
    }
    // 重新加载
    sl.fm.reload(sVO);
  }

  /**
   * 建立Java源文件
   * 
   * @param sVO 脚本信息类
   * @return java源文件路径 2014年7月24日
   * @author 马宝刚
   */
  protected void buildSource(ScriptVO sVO) {
    if (sVO.independentClass) {
      buildIndependentSource(sVO);
      return;
    }
    // java文件对象
    File sourceFile = null;
    // 先删除
    try {
      // 源文件对象
      File file = new File(sl.getClassBasePath() + sVO.buildSourceFilePath);
      if (file.isFile()) {
        // 删除，原本在这里用的是filesUtil中的删除功能，但万一这是一个路径
        // 会把整个路径下的全部文件都删除
        file.delete();
      }
    } catch (Exception e) {
    }
    sourceFile = SFilesUtil.createFile(sl.getClassBasePath() + sVO.buildSourceFilePath);

    // 写入流
    PrintStream out = null;
    try {
      out = new PrintStream(sourceFile, "UTF-8");

      outSource(sVO, new PrintStreamTool(out)); // 输出内容

    } catch (Exception e) {
      e.printStackTrace();
      sVO.addErrorMsg("The Script ID:[" + sVO.id + "] Create Build Java File:[" + sl.getClassBasePath()
          + sVO.buildSourceFilePath + "] Exception:" + DebugUtil.getExceptionInfo(e, "\n"));
      log.error("The Script ID:[" + sVO.id + "] Create Build Java File:[" + sl.getClassBasePath()
          + sVO.buildSourceFilePath + "] Exception", e);
      return;
    } finally {
      out.flush();
      out.close();
    }
    // 重新加载
    sl.fm.reload(sVO);
  }
    
    /**
     * 解析脚本内容并返回java源文件相关信息
     * 
     * 在此根据代码内容，重新整理引用其它脚本信息
     * 
     * 0 类中其它方法代码 1 主方法代码
     * 
     * @param sVO 脚本信息类
     * @return java源文件相关信息
     * @throws Exception 异常 2014年7月24日
     * @author 马宝刚
     */
    protected StringBuffer[] parseSource(ScriptVO sVO, List<String> importList) throws Exception {
      // 构建返回值
      StringBuffer[] reSbfs = new StringBuffer[4];

      reSbfs[0] = new StringBuffer(); // 引用信息
      reSbfs[1] = new StringBuffer(); // 方法内容

      /*
       * 处理引用其它脚本类路径信息 （无需在脚本类中import其它脚本类，因为是通过间接方式调用的）
       */

      // 加入常用类路径
      importList.add("import java.util.*;");
      importList.add("import java.text.*;");
      importList.add("import java.io.*;");
      importList.add("import com.jphenix.standard.script.*;");
      importList.add("import com.jphenix.servlet.multipart.instancea.*;");
      importList.add("import com.jphenix.standard.exceptions.*;");
      importList.add("import com.jphenix.share.lang.*;");
      importList.add("import com.jphenix.share.tools.*;");
      importList.add("import com.jphenix.share.util.*;");
      importList.add("import com.jphenix.driver.json.*;");
      importList.add("import com.jphenix.driver.nodehandler.*;");
      importList.add("import com.jphenix.standard.viewhandler.*;");
      importList.add("import com.jphenix.standard.servlet.*;");
      importList.add("import com.jphenix.standard.docs.*;");
      importList.add("import com.jphenix.kernel.script.*;");
      importList.add("import com.jphenix.standard.db.*;");
      importList.add("import com.jphenix.driver.threadpool.ThreadSession;");
      importList.add("import com.jphenix.servlet.common.*;");
      importList.add("import com.jphenix.servlet.parent.*;");
      importList.add("import com.jphenix.kernel.objectloader.interfaceclass.*;");
      importList.add("import com.jphenix.share.tools.Base64;");

      // 导入需要固定导入的类路径
      for (String impStr : sl.getImportList()) {
        importList.add("import " + impStr + ";");
      }

      // 设置处理单个传入参数
      if (!sVO.isClassVar) {
        for (ScriptFieldVO sfVO : sVO.inFields) {
          reSbfs[1].append(getFieldContent(sfVO, sVO));
          if (sVO.hasError()) {
            return reSbfs;
          }
        }
      }

      // 准备重新整理引用脚本信息
      sVO.importScriptIdList = new ArrayList<String>();

      String sourceCode = sVO.sourceContent; // 代码内容
      if (sourceCode != null) {
        // 如果在页面中提交脚本内容时，采用了base64加密，在这里要对内容解密
        if (sourceCode.startsWith("@b64@")) {
          sourceCode = sourceCode.substring(5);
          sourceCode = Base64.base64Decode(sourceCode, "UTF-8");
        } else {
          // 讲代码中的被转义的 <![CDATA[ ]]> 转义回来
          sourceCode = BaseUtil.swap(sourceCode, "<!^^CDATA[", "<![CDATA[", "]^^>", "]]>");
        }
        // 去掉开头和末尾的换行符
        if (sourceCode.startsWith("\n")) {
          sourceCode = sourceCode.substring(1);
        }
        if (sourceCode.endsWith("\n")) {
          sourceCode = sourceCode.substring(0, sourceCode.length() - 1);
        }
        // 去掉代码中/* */注释内容
        sourceCode = fixComments(sourceCode);

        int point = 0; // 分割点
        while (sourceCode.length() > 0) {
          point = sourceCode.indexOf("<%!");
          if (point > -1) {
            // 存在其它脚本
            if (point > 0) {
              reSbfs[1].append(sourceCode, 0, point);
            }
            sourceCode = sourceCode.substring(point + 3);
            point = sourceCode.indexOf("!%>");
            if (point < 0) {
              sVO.addErrorMsg("The Script id:[" + sVO.id + "] Find The Key  <%! But Not Find The Key !%>");
              return reSbfs;
            }
            reSbfs[0].append(sourceCode, 0, point);
            sourceCode = sourceCode.substring(point + 3);
          } else {
            reSbfs[1].append(sourceCode);
            break;
          }
        }
      }
      reSbfs[0] = parseSourceLine(sVO, importList, reSbfs[0]);
      reSbfs[1] = parseSourceLine(sVO, importList, reSbfs[1]);
      return reSbfs;
    }

    /**
     * 逐行解析代码
     * 
     * @param sVO        脚本信息类
     * @param importList 导入类信息序列
     * @param sourceSbf  代码信息块
     * @return 解析后的代码
     * @throws Exception 异常 2014年8月22日
     * @author 马宝刚
     */
    private StringBuffer parseSourceLine(ScriptVO sVO, List<String> importList, StringBuffer sourceSbf)
        throws Exception {
      // 构建返回值
      StringBuffer reSbf = new StringBuffer();
      /*
       * 处理方法内容 去掉注释 / * * /
       */
      List<String> lineList = BaseUtil.splitToList(ScriptUtil.fixZs(sourceSbf.toString()), "\n");
      String checkStr; // 检测字符串
      int zs1Point = -1; // 注释位置 //
      int zyPoint = -1; // 转译位置
      int zyOverPoint = -1; // 转移结束位置
      StringBuffer zyContent = null; // 转移内容
      boolean inZy = false; // 是否为转译字符

      // 当一行语句中包含多段转译脚本，就需要多次处理该行
      boolean needAgain = false; // 是否需要继续处理行

      for (String line : lineList) {
        do {
          if (line.length() < 1 || "\r".equals(line)) {
            needAgain = false;
            continue;
          }
          checkStr = BaseUtil.trim(line, " ", " ");
          if (checkStr.startsWith("import ")) {
            // 导入类
            importList.add(checkStr);
            needAgain = false;
            continue;
          }
          zyOverPoint = line.indexOf("%>");
          if (zyOverPoint > 0) {
            if (inZy) {
              zyContent.append(line, 0, zyOverPoint);
              reSbf.append(parseZy(zyContent.toString(), sVO));
              if (sVO.hasError()) {
                return reSbf;
              }
              line = line.substring(zyOverPoint + 2);
              inZy = false;
              // 在截取掉当前转义末尾标识符后，重新检测当前行是否还存在转义末尾标识符
              zyOverPoint = line.indexOf("%>");
            }
          }
          zyPoint = line.indexOf("<%");
          if (sVO.hasError()) {
            return reSbf;
          }
          if (zyPoint > -1) {
            zyContent = new StringBuffer();
            // 发现转译字符
            if (zyOverPoint > 0) {
              // 单行结束
              zyContent.append(line, zyPoint + 2, zyOverPoint);
              reSbf
                  // 原本在这里做了trim，但是如果遇到 return <%()%> return被trim后就跟后面转义的语句连在了一起，导致报错
                  .append(line, 0, zyPoint);
              reSbf.append(parseZy(zyContent.toString(), sVO));

              if (sVO.hasError()) {
                return reSbf;
              }
              // 单行中，存在多个转译段，需要再一次做处理
              line = line.substring(zyOverPoint + 2);
              zyPoint = line.indexOf("<%");
              if (zyPoint > -1) {
                // 需要再次处理转译
                needAgain = true;
              } else {
                // 单行结束
                reSbf.append(line).append("\n");
                needAgain = false;
              }
              continue;
            } else {
              inZy = true;
              // 原本在这里做了trim，这样会导致语句前后连在一起
              reSbf.append(line, 0, zyPoint);
              zs1Point = line.indexOf("//");
              if (zs1Point > 0) {
                zyContent.append(line, zyPoint + 2, zs1Point);
              } else {
                zyContent.append(line.substring(zyPoint + 2));
              }
              needAgain = false;
              continue;
            }
          }
          if (inZy) {
            /*
             * //为毛这么做， 非得过滤 // 如果内容中有http://结果后面的东西就被过滤调了
             * //另外需要将换行符还给人家，凭什么在这里干掉换行符，比如内容中是一个复杂的字符串，人家就要保留换行符的 zs1Point =
             * line.indexOf("//"); if(zs1Point>0) {
             * zyContent.append(line.substring(0,zs1Point)); }else { zyContent.append(line);
             * }
             */
            zyContent.append(line).append("\n");
          } else {
            line = BaseUtil.trim(line, " ", " ");
            if (line.length() > 0) {
              reSbf.append("      ").append(BaseUtil.swapString(line, "＜％", "<%", "％＞", "%>")).append("\n");
            }
          }
          needAgain = false;
        } while (needAgain);
        needAgain = false;
      }
      return reSbf;
    }

    /**
     * 过滤掉内容中的注释信息
     * 
     * @param content 内容
     * @return 过滤后的内容 2016年10月2日
     * @author MBG
     */
    private String fixComments(String content) {
      if (content == null || content.length() < 1) {
        return "";
      }
      // 构建返回值
      StringBuffer reSbf = new StringBuffer();
      int point = content.indexOf("/*");
      while (point > -1) {
        reSbf.append(content, 0, point);
        content = content.substring(point + 2);

        point = content.indexOf("*/");
        if (point > -1) {
          content = content.substring(point + 2);
        }
        point = content.indexOf("/*");
      }
      reSbf.append(content);
      return reSbf.toString();
    }
    
    /**
     * 解析转译字符内容
     * 
     * @param zyContent 转译字符
     * @param sVO       脚本信息类
     * 
     *                  如果转译代码前的字符是分号，说明当前函数为过程函数，在 解析调用脚本转译代码时，就不在代码前加上强制转换语句了。
     * 
     * @return 转译后的内容
     * @throws Exception 异常 2014年7月27日
     * @author 马宝刚
     */
    private String parseZy(String zyContent, ScriptVO sVO) throws Exception {
      // 构建返回值
      StringBuffer reSbf = new StringBuffer();

      // ------------------------------------------------------------------------------------------------------------
      if (zyContent.startsWith("\"")) {
        // 复杂字符串 String str = <%" 复杂字符串 "%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // 去掉开头的双引号
        zyContent = zyContent.substring(1, zyContent.lastIndexOf("\""));
        // <@变量名 #注释>
        int sPoint = zyContent.indexOf("<@"); // 变量定义开始
        int ePoint = zyContent.indexOf(">", sPoint); // 变量定义结束
        int tPoint; // 临时节点
        reSbf.append(" ");
        String varName; // 转译变量名
        while (sPoint > -1 && ePoint > 0) {
          varName = zyContent.substring(sPoint + 2, ePoint);
          // 为了支持 <@key@> 格式的参数 与SQL拼写方法保持一致 保持一致总是好的
          if (varName.endsWith("@")) {
            varName = varName.substring(0, varName.length() - 1);
          }
          tPoint = varName.indexOf("#");
          if (tPoint > -1) {
            varName = BaseUtil.trim(varName.substring(0, tPoint), " ", "\t", "\r", "\n");
          }
          if (varName.length() > 0) {
            // 在变量声明中的注释 <@para1 //注释>
            int zPoint = varName.indexOf("//");
            if (zPoint > -1) {
              varName = BaseUtil.trim(varName.substring(0, zPoint), " ", "\t", "\r", "\n");
            }
            reSbf.append(BaseUtil.swapString(zyContent.substring(0, sPoint), "《", "<", "》", ">")).append("\"+")
                .append(varName).append("+\"");
          } else {
            reSbf.append(BaseUtil.swapString(zyContent.substring(0, sPoint), "《", "<", "》", ">"));
          }
          zyContent = zyContent.substring(ePoint + 1);
          sPoint = zyContent.indexOf("<@");
          ePoint = zyContent.indexOf(">", sPoint);
        }
        reSbf.append(BaseUtil.swapString(zyContent, "《", "<", "》", ">"));
        // 结尾不能放；符号，因为拼装的字符串有可能作为参数，放在语句之中
        return "\"" + BaseUtil.swapString(reSbf.toString(), "\r", "", "\n", "") + "\"";
        // ----------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("$")) {
        // 复杂字符串（内部带单引号或双引号的） String str = <%$ 复杂字符串 $%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // 去掉开头的双引号
        zyContent = zyContent.substring(1, zyContent.lastIndexOf("$"));
        return fixZyStr(zyContent);
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("H$")) {
        // 拼装HTML/XML解析对象（内部带单引号或双引号的） IViewHandler reVh = <%H$ 复杂字符串 $H%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // H 必须大写哈
        // 去掉开头的双引号
        try {
          zyContent = zyContent.substring(2, zyContent.lastIndexOf("$H"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%H$...$H%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " (new com.jphenix.driver.nodehandler.instancea.NodeHandler()).setNodeBody(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("H")) {
        // 拼装HTML/XML解析对象（内部带单引号或双引号的） IViewHandler reVh = <%H 复杂字符串 H%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // H 必须大写哈
        // 去掉开头的双引号
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("H"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%H...H%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " (new com.jphenix.driver.nodehandler.instancea.NodeHandler()).setNodeBody(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("X$")) {
        // 拼装HTML/XML解析对象（内部带单引号或双引号的） IViewHandler reVh = <%X$ 复杂字符串 $X%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // H 必须大写哈
        // 去掉开头的双引号
        try {
          zyContent = zyContent.substring(2, zyContent.lastIndexOf("$X"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%X$...$X%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " (new com.jphenix.driver.nodehandler.instancea.NodeHandler()).setNodeBody(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("X")) {
        // 拼装HTML/XML解析对象（内部带单引号或双引号的） IViewHandler reVh = <%X 复杂字符串 X%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // H 必须大写哈
        // 去掉开头和末尾的关键字
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("X"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%X...X%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " (new com.jphenix.driver.nodehandler.instancea.NodeHandler()).setNodeBody(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("JS")) {
        // 用在动作脚本中，可以在其中编写JS代码，作为字符串输出（调用printjs(jsContent) ）
        // 去掉开头和末尾的关键字
        try {
          zyContent = zyContent.substring(2, zyContent.lastIndexOf("JS"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%JS...JS%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);
        return " printjs(" + zyContent + ");";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("J$")) {
        // 拼装Json解析对象（内部带单引号或双引号的） Json reJson = <%J$ 复杂字符串 $J%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // J 必须大写哈
        // 去掉开头的双引号
        try {
          zyContent = zyContent.substring(2, zyContent.lastIndexOf("$J"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%J$...$J%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " new com.jphenix.driver.json.Json(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("J{")) {
        // 拼装Json解析对象（内部带单引号或双引号的） Json reJson = <%J{ 复杂字符串 }J%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // J 必须大写哈
        // 去掉开头的双引号
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("}J") + 1);
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%J{...}J%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " new com.jphenix.driver.json.Json(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("J[")) {
        // 拼装Json解析对象（内部带单引号或双引号的） Json reJson = <%J[ 复杂字符串 ]J%>;
        // 注意：如果字符串中需要大于号或小于号，请用全角的符号，会自动替换为半角的哈
        // 末尾需要写分号
        // J 必须大写哈
        // 去掉开头的双引号
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("]J") + 1);
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%J[...]J%> Content Error:[" + zyContent + "]");
        }
        zyContent = fixZyStr(zyContent);

        return " new com.jphenix.driver.json.Json(" + zyContent + ")";
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("SQL$")) {
        // 处理复杂SQL语句
        // <@ "","" @>
        try {
          zyContent = zyContent.substring(4, zyContent.lastIndexOf("$SQL"));
        } catch (Exception e) {
          throw new RuntimeException("Parse SQL Script <%SQL$...$SQL%> Content Error:[" + zyContent + "]");
        }
        return fixSqlStr(zyContent);
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("S$")) {
        // 处理复杂SQL语句
        // <@ "","" @>
        try {
          zyContent = zyContent.substring(2, zyContent.lastIndexOf("$S"));
        } catch (Exception e) {
          throw new RuntimeException("Parse SQL Script <%S$...$S%> Content Error:[" + zyContent + "]");
        }
        return fixSqlStr(zyContent);
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("S")) {
        // 处理复杂SQL语句
        // <@ "","" @>
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("S"));
        } catch (Exception e) {
          throw new RuntimeException("Parse SQL Script <%S...S%> Content Error:[" + zyContent + "]");
        }
        return fixSqlStr(zyContent);
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("{{")) {
        // 构造SListMap容器
        // SListMap paraMap = <%{{"key1":"value1","key2":<@para2>}}%>;
        // 注意： 末尾需要写分号。
        // 获取构造信息结束标识
        try {
          zyContent = zyContent.substring(2, zyContent.lastIndexOf("}}")); // 去掉首尾的双大括号
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%{{...}}%> Content Error:[" + zyContent + "]");
        }
        // 处理拼装SMapList所需的传入参数
        String[] mapInfos = fixMapParas(zyContent, sVO);
        reSbf.append("ScriptUtil.fixSListMap(new String[]{").append(mapInfos[0]).append("},new Object[]{")
            .append(mapInfos[1]).append("},null)");
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("{")) {
        // 构造Map容器
        // Map paraMap = <%{{"key1":"value1","key2":<@para2>}}%>;
        // 注意：脚本中不要使用泛型，避免不匹配
        // 末尾需要写分号
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("}")); // 去掉首尾大括号
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%{...}%> Content Error:[" + zyContent + "]");
        }
        // 拼装map传入参数信息
        String[] mapInfos = fixMapParas(zyContent, sVO);
        reSbf.append(" ScriptUtil.fixMap(new String[]{").append(mapInfos[0]).append("},new Object[]{")
            .append(mapInfos[1]).append("},null)");
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("[")) {
        // 构造List序列
        // List paraList = <%["value1",<@para2>]%>;
        // 注意：脚本中不要使用泛型，避免不匹配
        // 末尾需要写分号
        // 获取构造信息结束标识，好获取构造后的返回值的变量声明信息
        // 去掉首尾 [] 符号
        try {
          zyContent = zyContent.substring(1, zyContent.lastIndexOf("]"));
        } catch (Exception e) {
          throw new RuntimeException("Parse Script <%[...]%> Content Error:[" + zyContent + "]");
        }
        // 参数值序列
        StringBuffer valueSbf = new StringBuffer();
        // 参数元素序列
        List<String> valueList = StringUtil.split(zyContent, ",", new String[] { "\"", "\'", "<"},
            new String[] { "\"", "\'", ">" });
        int tPoint; // 临时节点
        for (String value : valueList) {
          value = BaseUtil.trim(value, "\t", " ", "\"", "\r", "\n");
          if (value.startsWith("<@")) {
            value = value.substring(2, value.length() - 1);
            // 为了支持 <@key@> 格式的参数 与SQL拼写方法保持一致 保持一致总是好的
            if (value.endsWith("@")) {
              value = value.substring(0, value.length() - 1);
            }
            // 在变量声明中的注释 <@para1 #注释>
            tPoint = value.indexOf("#");
            if (tPoint > -1) {
              value = BaseUtil.trim(value.substring(0, tPoint), " ", "\t", "\r", "\n");
            }
          } else {
            value = "\"" + value + "\"";
          }
          if (valueSbf.length() > 0) {
            valueSbf.append(",");
          }
          valueSbf.append(value);
        }
        reSbf.append(" ScriptUtil.fixList(new Object[]{").append(valueSbf).append("},null)");
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("@")) {
        // 调用路由脚本 通过 ProgramRouteFilter
        /*
         * 比如：
         * 
         * Object res = <%@center(100001){key:value}%>; 调用目标集群群组名为center的脚本 100001，并传入参数
         * key:value
         * 
         * Object res = <%@(100002){key:value}%>;
         * 表用目标集群的脚本100002，并传入参数。框架会通过100002脚本名去配置文件中匹配 代理到哪个群组。
         * 
         * Object res = <@center(100001){key:value}-a-%>; 广播调用集群中center分组中100001脚本
         * 
         * Object res = <@center(100001){key:value}-m-%>;
         * 调用集群中center分组中主服务器100001脚本（实际上，-m- 是多余的，即使不加，调用的也是主服务器中的这个脚本）
         * 
         * <@@center(100001){key:value}-a-%>;
         * 无需返回值（因为是目标服务器，本机是无法通过脚本信息类判定目标脚本带不带返回值，只能手工强制无需返回值）
         * 这中情况主要用于反向应触发调用服务，只要通知前置机，无需获取返回值。这样就无需轮询等待返回值，使远程调用 效率大大提高。
         * 
         * <@@center(100001){key:value}%>; 无需返回值，调用目标服务器方法
         */

        // 去掉头一个@
        zyContent = zyContent.substring(1);

        String scriptId = null; // 脚本主键
        String groupKey = null; // 群组名
        int point = zyContent.indexOf("("); // 字符串分割索引
        if (point < 0) {
          throw new Exception(
              "Parse RouteScript Content Exception:Not Find The Function Symbol () \")\"  Content:[" + zyContent + "]");
        }
        if (point > 0) {
          groupKey = zyContent.substring(0, point).trim();
          zyContent = zyContent.substring(point + 1);
        } else {
          groupKey = "";
          zyContent = zyContent.substring(1);
        }
        boolean noReturn = false; // 是否不需要返回值
        if (groupKey.startsWith("@")) {
          // 第二个@
          noReturn = true;
          groupKey = groupKey.substring(1);
        }
        // 解析脚本主键
        point = zyContent.indexOf(")");
        if (point < 1) {
          throw new Exception("Parse RouteScript Content Exception:Not Find The End Point\")\" For ScriptID Content:["
              + zyContent + "]");
        }
        scriptId = zyContent.substring(0, point);
        zyContent = zyContent.substring(point + 1);

        if (scriptId.length() > 0 && !sVO.importRouteScriptIdList.contains(scriptId)) {
          sVO.importRouteScriptIdList.add(scriptId);
        }
        point = zyContent.lastIndexOf("}");
        if (point < 0) {
          // 没有传入参数声明
          point = zyContent.lastIndexOf(")");
        }
        // 截至这里zyContent的值
        // (100001){key:value}-a- (100001)-a- (100001) (100001){key:value}

        String suffix; // 后缀
        if (point + 1 < zyContent.length()) {
          suffix = zyContent.substring(point + 1);
          zyContent = zyContent.substring(0, point + 1);
        } else {
          suffix = "";
        }
        String invokeType = "0"; // 调用模式：0普通调用 1调用调用主服务器 2广播调用
        if ("-m-".equals(suffix) || "-1-".equals(suffix)) {
          // 调用集群主服务器脚本
          invokeType = "1";
        } else if ("-a-".equals(suffix) || "-2-".equals(suffix)) {
          // 广播调用集群中全部服务器脚本
          invokeType = "2";
        }
        // 传入参数
        String[] mapInfos;
        // 判断是否存在传入参数
        point = zyContent.indexOf("{");

        if (point > -1) {
          zyContent = zyContent.substring(point + 1, zyContent.lastIndexOf("}"));
          mapInfos = fixMapParas(zyContent, sVO);
        } else {
          mapInfos = new String[2];
        }
        reSbf.append("_scriptLoader.invokeRouteScript(\"" + groupKey + "\",this,\"" + scriptId
            + "\",ScriptUtil.fixMap(new String[]{" + mapInfos[0] + "},new Object[]{" + mapInfos[1] + "},null),"
            + invokeType + "," + (noReturn ? "true" : "false") + ")");
        // ------------------------------------------------------------------------------------------------------------
      } else if (zyContent.startsWith("(") || zyContent.startsWith("*(") || zyContent.startsWith("&(")
          || zyContent.startsWith("&&(")) {
        // 调用其它脚本 Object res = <%(100002){para1:value1,para2:<@paravalue2>}%>;
        // Object res = <%(100002)%>;
        // <%(100002)%>;
        // 注意：脚本中不要使用泛型，避免不匹配
        // 末尾需要写分号

        // 注意：如果这样写，是不带返回值的，无论脚本是否声明了返回值，通常用在过程调用
        // <%*(100002){aa:bb}%>;
        boolean noRes = false; // 是否不需要返回值
        if (zyContent.startsWith("*(")) {
          noRes = true;
          zyContent = zyContent.substring(1);
        }
        int point = zyContent.indexOf(")"); // 脚本代码结束符分割点

        String scriptId; // 脚本主键

        // <%&&(100002)%> 返回脚本 100002 类实例数组（只有一个脚本主键，也返回数组）
        if (zyContent.startsWith("&&(")) {
          scriptId = zyContent.substring(3, point);
          // 判断是否返回多个脚本类实例
          String[] ids = BaseUtil.split(scriptId, ",");
          boolean noFirst = false; // 是否首个数组元素
          reSbf.append("new Object[]{");
          for (int i = 0; i < ids.length; i++) {
            ids[i] = BaseUtil.trim(ids[i], " ", "\t", "\r", "\n");
            if (ids[i].length() > 0) {
              if (noFirst) {
                reSbf.append(",");
              } else {
                noFirst = true;
              }
              // 随后通过路由调用其它服务器的脚本，而且代码中可以不加指定调用哪台服务器脚本，就跟调用本地
              // 脚本一样，这就无法判断出这个目标脚本是不是有效的，所以这个警告也没什么意义了 2020-08-08 mbg
              // if(!sl.hasScript(ids[i])) {
              // warning("ScriptID:["+sVO.id+"] Import ScriptID:["+ids[i]+"] Not Found In
              // LocalServer");
              // }
              if (!sVO.importScriptIdList.contains(ids[i])) {
                sVO.importScriptIdList.add(ids[i]);
              }
              reSbf.append("_scriptLoader.getScriptNoException(\"" + ids[i] + "\")");
            }
          }
          return reSbf.append("}").toString();
        }
        // <%&(100002)%> 或 <%&(100002,100003)%> 返回脚本 100002 类实例或数组
        if (zyContent.startsWith("&(")) {
          scriptId = zyContent.substring(2, point);
          if (scriptId.endsWith(".class")) {
            /*
             * 该格式 <%&(ScriptCls.class)%> 是在其它脚本中获取传统脚本类实例 这样写等同于 bean(ScriptCls.class);
             * 但采用<%&(方式获取类实力有一个优势，就是热修改需要获取的传统类后，引用了该传统类的脚本
             * 都会自动刷新，避免出现因传统类修改，类加载器被更新，引用的脚本都报获取到的类实例不匹配的错误
             */
            reSbf.append("bean(").append(scriptId).append(")");
            return reSbf.toString();
          } else {
            // 判断是否返回多个脚本类实例
            String[] ids = BaseUtil.split(scriptId, ",");
            if (ids.length < 2) {
              // 返回脚本类实例

              // 2020-06-16 mbg
              // 随后通过路由调用其它服务器的脚本，而且代码中可以不加指定调用哪台服务器脚本，就跟调用本地
              // 脚本一样，这就无法判断出这个目标脚本是不是有效的，所以这个警告也没什么意义了
              // if(!sl.hasScript(scriptId)) {
              // warning("ScriptID:["+sVO.id+"] Import ScriptID:["+scriptId+"] Not Found");
              // }

              if (!sVO.importScriptIdList.contains(scriptId)) {
                sVO.importScriptIdList.add(scriptId);
              }
              // 参数信息分隔符
              point = zyContent.indexOf("{");
              if (point > -1 && sl.hasScript(scriptId)) {
                // 注意：返回类实例的代码中，传入的参数都放在线程容器中： key:脚本主键，value：Map:key: 参数名: value:参数值
                zyContent = zyContent.substring(point + 1, zyContent.lastIndexOf("}"));
                //不用在外部判断入参代码格式，在fixMapParas方法中已经判断了
                //if (zyContent.indexOf(":") > -1 || zyContent.indexOf("!") > -1) {
                  if(zyContent.length()>0){
                  // 设置传入参数<%&(100001){"key1":"value1","key2":"value2"}%>
                  String[] mapInfos = fixMapParas(zyContent, sVO);
                  reSbf.append("_scriptLoader.getScriptNoException(\"" + scriptId + "\",ScriptUtil.fixMap(new String[]{"
                      + mapInfos[0] + "},new Object[]{" + mapInfos[1] + "},null))");
                } else {
                  reSbf.append("_scriptLoader.getScriptNoException(\"" + scriptId + "\")");
                }
              } else {
                reSbf.append("_scriptLoader.getScriptNoException(\"" + scriptId + "\")");
              }
              return reSbf.toString();
            }
            boolean noFirst = false; // 是否首个数组元素
            reSbf.append("new Object[]{");
            for (int i = 0; i < ids.length; i++) {
              ids[i] = BaseUtil.trim(ids[i], " ", "\t", "\r", "\n");
              if (ids[i].length() > 0) {
                if (noFirst) {
                  reSbf.append(",");
                } else {
                  noFirst = true;
                }
                // 随后通过路由调用其它服务器的脚本，而且代码中可以不加指定调用哪台服务器脚本，就跟调用本地
                // 脚本一样，这就无法判断出这个目标脚本是不是有效的，所以这个警告也没什么意义了 2020-08-08 mbg
                // if(!sl.hasScript(ids[i])) {
                // warning("ScriptID:["+sVO.id+"] Import ScriptID:["+ids[i]+"] Not Found");
                // }
                if (!sVO.importScriptIdList.contains(ids[i])) {
                  sVO.importScriptIdList.add(ids[i]);
                }
                reSbf.append("_scriptLoader.getScriptNoException(\"" + ids[i] + "\")");
              }
            }
            return reSbf.append("}").toString();
          }
        }
        scriptId = zyContent.substring(1, point);

        if (scriptId.endsWith(".class")) {
          /*
           * 该格式 <%&(ScriptCls.class)%> 是在其它脚本中获取传统脚本类实例 这样写等同于 bean(ScriptCls.class);
           * 但采用<%&(方式获取类实力有一个优势，就是热修改需要获取的传统类后，引用了该传统类的脚本
           * 都会自动刷新，避免出现因传统类修改，类加载器被更新，引用的脚本都报获取到的类实例不匹配的错误
           */
          reSbf.append("bean(").append(scriptId).append(")");
          return reSbf.toString();
        }

        // 随后通过路由调用其它服务器的脚本，而且代码中可以不加指定调用哪台服务器脚本，就跟调用本地
        // 脚本一样，这就无法判断出这个目标脚本是不是有效的，所以这个警告也没什么意义了 2020-08-08 mbg
        // if(!sl.hasScript(scriptId)) {
        // warning("ScriptID:["+sVO.id+"] Import ScriptID:["+scriptId+"] Not Found");
        // }
        if (!sVO.importScriptIdList.contains(scriptId)) {
          sVO.importScriptIdList.add(scriptId);
        }
        // <%(100002)$%> 返回脚本 100002 的类实例
        if (zyContent.substring(point + 1).startsWith("$")) {
          reSbf.append("_scriptLoader.getScriptNoException(\"" + scriptId + "\")");
          return reSbf.toString();
        }
        // 通常用于 private S100002 s100002 = <%(100002)*%>;
        if (zyContent.substring(point + 1).startsWith("*")) {
          reSbf.append("(" + ScriptVO.SCRIPT_CLASS_HEAD).append(scriptId)
              .append(")_scriptLoader.getScriptNoException(\"" + scriptId + "\")");
          return reSbf.toString();
        }

        // 获取传参结束标识，好获取调用脚本返回值的变量声明信息
        point = zyContent.lastIndexOf("}");
        if (point < 0) {
          // 没有传入参数声明
          point = zyContent.lastIndexOf(")");
        }
        // 截至这里zyContent的值
        // (100001){key:value}-a- (100001)-a- (100001) (100001){key:value}

        String suffix; // 后缀
        if (point + 1 < zyContent.length()) {
          suffix = zyContent.substring(point + 1);
          zyContent = zyContent.substring(0, point + 1);
        } else {
          suffix = "";
        }

        String invokeType = "0"; // 调用模式：0普通调用 1调用调用主服务器 2广播调用
        if ("-m-".equals(suffix) || "-1-".equals(suffix)) {
          // 调用集群主服务器脚本
          invokeType = "1";
        } else if ("-a-".equals(suffix) || "-2-".equals(suffix)) {
          // 广播调用集群中全部服务器脚本
          invokeType = "2";
        } else if ("?".equals(suffix)) {
          // 如果在Action中调用服务，服务中的参数名跟提交到action的参数名一样时，就可以用这个方法
          // Object res = <%(100002)?%>;

          // 注意：这种方式暂不支持集群操作

          if (sl.hasScript(scriptId)) {
            // 获取要调用的脚本信息类
            ScriptVO callSVO = sl.getScriptInfo(scriptId, false);
            if (callSVO == null) {
              sVO.addErrorMsg("ScriptID:[" + sVO.id + "] Call The ScriptID:[" + scriptId + "] Not Found");
              return "";
            }
            String resType = callSVO.outType; // 返回类型
            if (noRes || (resType == null || resType.length() < 1 || "Object".equals(resType))) {
              reSbf.append(
                  "_scriptLoader.invokeScript(this,\"" + scriptId + "\",ac,0," + (noRes ? "true" : "false") + ")");
            } else {
              reSbf.append("(" + resType + ")_scriptLoader.invokeScript(this,\"" + scriptId + "\",ac,0,false)");
            }
          } else {
            // 在这里预留集群远程调用的处理方法
            // 在启动时编译代码，有些脚本中实际上引用的是其它服务器的脚本，导致本地脚本加载器
            // 获取不到这个脚本，也就无法知道这个脚本返回值的类型，所以不在这里标记返回值类型
            reSbf.append(
                "_scriptLoader.invokeScript(this,\"" + scriptId + "\",ac,0," + (noRes ? "true" : "false") + ")");
          }
          return reSbf.toString();
        }

        // 截至这里zyContent的值
        // (100001){key:value} (100001)

        // 参数信息分隔符
        point = zyContent.indexOf("{");
        if (point > -1) {
          zyContent = zyContent.substring(point + 1, zyContent.lastIndexOf("}"));

          //不用在外部判断入参代码格式，在fixMapParas方法中已经判断了
          //if (zyContent.indexOf(":") > -1 || zyContent.indexOf("!") > -1) {
          if(zyContent.length()>0){
            // 设置传入参数<%(100001){"key1":"value1","key2":"value2"}%>
            String[] mapInfos = fixMapParas(zyContent, sVO);

            if (sl.hasScript(scriptId)) {
              // 获取要调用的脚本信息类
              ScriptVO callSVO = sl.getScriptInfo(scriptId, false);
              if (callSVO == null) {
                sVO.addErrorMsg("ScriptID:[" + sVO.id + "] Call The ScriptID:[" + scriptId + "] Not Found");
                return "";
              }
              String resType = callSVO.outType; // 返回类型
              if (noRes || (resType == null || resType.length() < 1 || "Object".equals(resType))) {
                reSbf.append("_scriptLoader.invokeScript(this,\"" + scriptId + "\",ScriptUtil.fixMap(new String[]{"
                    + mapInfos[0] + "},new Object[]{" + mapInfos[1] + "},null)," + invokeType + ","
                    + (noRes ? "true" : "false") + ")");
              } else {
                reSbf.append("(" + resType + ")_scriptLoader.invokeScript(this,\"" + scriptId
                    + "\",ScriptUtil.fixMap(new String[]{" + mapInfos[0] + "},new Object[]{" + mapInfos[1] + "},null),"
                    + invokeType + ",false)");
              }
            } else {
              // 在启动时编译代码，有些脚本中实际上引用的是其它服务器的脚本，导致本地脚本加载器
              // 获取不到这个脚本，也就无法知道这个脚本返回值的类型，所以不在这里标记返回值类型
              reSbf.append("_scriptLoader.invokeScript(this,\"" + scriptId + "\",ScriptUtil.fixMap(new String[]{"
                  + mapInfos[0] + "},new Object[]{" + mapInfos[1] + "},null)," + invokeType + ","
                  + (noRes ? "true" : "false") + ")");
            }
          }
          // 不支持这种写法
          // else {
          // //直接将参数对象传入
          // //Object res = <%(100001){para}%>; （注意，只能传入Map）
          // //<%(100001){para}%>; 无返回值执行
          // String paraName = BaseUtil.trim(zyContent,"\r","\t","\n");
          // //获取要调用的脚本信息类
          // ScriptVO callSVO = sl.getScriptInfo(scriptId,false);
          // if(callSVO==null) {
          // sVO.addErrorMsg("ScriptID:["+sVO.id+"] Call The ScriptID:["+scriptId+"] Not
          // Found");
          // return "";
          // }
          // String resType = callSVO.outType; //返回类型
          // if(noRes || (resType==null || resType.length()<1 ||
          // resType.equals("Object"))) {
          // reSbf.append("_scriptLoader.invokeScript(this,\""+scriptId+"\","+paraName+","+invokeType+")");
          // }else {
          // reSbf.append("("+resType+")_scriptLoader.invokeScript(this,\""+scriptId+"\","+paraName+","+invokeType+")");
          // }
          // }
        } else {
          // 不传参数 <%(100001)%>
          // 但实际上所有脚本都带传入参数的，如果脚本中没声明传入参数，则参数默认为Map类型

          if (sl.hasScript(scriptId)) {
            // 获取要调用的脚本信息类
            ScriptVO callSVO = sl.getScriptInfo(scriptId, false);
            if (callSVO == null) {
              sVO.addErrorMsg("ScriptID:[" + sVO.id + "] Call The ScriptID:[" + scriptId + "] Not Found");
              return "";
            }
            String resType = callSVO.outType; // 返回类型
            if (noRes || (resType == null || resType.length() < 1 || "Object".equals(resType))) {
              reSbf.append("_scriptLoader.invokeScript(this,\"" + scriptId + "\",new HashMap()," + invokeType + ","
                  + (noRes ? "true" : "false") + ")");
            } else {
              reSbf.append("(" + resType + ")_scriptLoader.invokeScript(this,\"" + scriptId + "\",new HashMap(),"
                  + invokeType + ",false)");
            }
          } else {
            // 在启动时编译代码，有些脚本中实际上引用的是其它服务器的脚本，导致本地脚本加载器
            // 获取不到这个脚本，也就无法知道这个脚本返回值的类型，所以不在这里标记返回值类型
            reSbf.append("_scriptLoader.invokeScript(this,\"" + scriptId + "\",new HashMap()," + invokeType + ","
                + (noRes ? "true" : "false") + ")");
          }
        }
      } else {
        warning("未知的动态脚本代码段。 id:[" + sVO.id + "] 脚本内容：\n" + zyContent);
      }
      return reSbf.toString();
    }
    
    /**
     * 将脚本中拼装map的语句整理为map所需要的值
     * 
     * @param zyContent 脚本语句段
     * @param sVO       脚本信息类
     * @return 拼装map需要的值 2016年11月21日
     * @author MBG
     */
    private String[] fixMapParas(String zyContent, ScriptVO sVO) {
      // 参数元素序列
      List<String> paraList = StringUtil.split(zyContent, ",", new String[] { "\"", "\'", "<" },
          new String[] { "\"", "\'", ">" });
      int cPoint; // 参数分割点
      // 构造传入参数信息段
      StringBuffer keySbf = new StringBuffer();
      StringBuffer valueSbf = new StringBuffer();
      String key; // 主键
      String value; // 值
      for (String para : paraList) {
        para = BaseUtil.trim(para, "\t", " ","\n","\r");
        cPoint = para.indexOf(":");
        if (cPoint < 0) {
          //如果变脸声明处不是 {key:value}，而是{key}，不带冒号分隔符
          //将{key}视为 {!key} 或者 {key:<@key@>}
          key = para;
          key = BaseUtil.trim(key, " ", "\t", "\"","\n","\r");
          if(!key.startsWith("!")){
            key = "!"+key;
          }
          value = "";
        } else {
          key = para.substring(0, cPoint);
          value = para.substring(cPoint + 1);
          key = BaseUtil.trim(key, " ", "\t", "\"","\n","\r");
          value = BaseUtil.trim(value, " ", "\t", "\"","\n","\r");
        }
        if (key.startsWith("!")) {
          // 参数值变量的名字就是主键名
          key = key.substring(1);
          if (valueSbf.length() > 0) {
            valueSbf.append(",");
          }
          valueSbf.append(key);
          if (keySbf.length() > 0) {
            keySbf.append(",");
          }
          keySbf.append("\"").append(key).append("\"");
          continue;
        } else if ("*".equals(key)) {
          // 如果容器元素中为 * 而并不是传统的 key:value 则说明需要将传入的参数都设置到容器中
          if (sVO.inFields != null) {
            for (ScriptFieldVO sfVO : sVO.inFields) {
              if (keySbf.length() > 0) {
                keySbf.append(",");
              }
              keySbf.append("\"").append(sfVO.name).append("\"");
              if (valueSbf.length() > 0) {
                valueSbf.append(",");
              }
              valueSbf.append(sfVO.name);
            }
          }
          continue;
        }
        int tPoint; // 分割点
        if (keySbf.length() > 0) {
          keySbf.append(",");
        }
        if (key.startsWith("<@")) {
          key = key.substring(2, key.length() - 1);
          // 为了支持 <@key@> 格式的参数 与SQL拼写方法保持一致 保持一致总是好的
          if (key.endsWith("@")) {
            key = key.substring(0, key.length() - 1);
          }
          // 在变量声明中的注释 <@para1 #注释>
          tPoint = key.indexOf("#");
          if (tPoint > -1) {
            key = BaseUtil.trim(key.substring(0, tPoint), " ", "\t", "\r", "\n");
          }
          keySbf.append(key);
        } else {
          // 加上字符串定界符
          keySbf.append("\"").append(key).append("\"");
        }
        if (valueSbf.length() > 0) {
          valueSbf.append(",");
        }
        if (value.startsWith("<@")) {
          value = value.substring(2, value.length() - 1);
          // 为了支持 <@key@> 格式的参数 与SQL拼写方法保持一致 保持一致总是好的
          if (value.endsWith("@")) {
            value = value.substring(0, value.length() - 1);
          }
          // 在变量声明中的注释 <@para1 #注释>
          tPoint = value.indexOf("#");
          if (tPoint > -1) {
            value = BaseUtil.trim(value.substring(0, tPoint), " ", "\t", "\r", "\n");
          }
          valueSbf.append(value);
        } else {
          valueSbf.append("\"").append(value).append("\"");
        }
      }
      return new String[] { keySbf.toString(), valueSbf.toString() };
    }
    
    /**
     * 解析转译脚本中的复杂字符串 （不带换行符）
     * @param zyContent 待解析的转译脚本
     * @return          解析后的脚本
     * 2015年3月20日
     * @author 马宝刚
     */
    private String fixZyStr(String zyContent) {
        //构建返回值
        StringBuffer reSbf = new StringBuffer();
        //<@变量名  #注释>
        int sPoint = zyContent.indexOf("<@"); //变量定义开始
        int ePoint = zyContent.indexOf(">" ,sPoint );  //变量定义结束
        int tPoint; //临时节点
        reSbf.append(" ");
        String varName; //转译变量名
        while(sPoint>-1 && ePoint>0) {
            varName = zyContent.substring(sPoint+2,ePoint);
            //为了支持 <@key@> 格式的参数 与SQL拼写方法保持一致 保持一致总是好的
            if(varName.endsWith("@")) {
                varName = varName.substring(0,varName.length()-1);
            }
            tPoint = varName.indexOf("#");
            if(tPoint>-1) {
                varName = BaseUtil.trim(varName.substring(0,tPoint)," ","\t","\r","\n");
            }
            if(varName.length()>0) {
                reSbf
                      .append(BaseUtil.swapString(zyContent.substring(0,sPoint),"\"","\\\"","\'","\\\'","《","<","》",">"))
                      .append("\"+")
                      .append(varName)
                      .append("+\"");
            }else {
                reSbf.append(BaseUtil.swapString(zyContent.substring(0,sPoint),"\"","\\\"","\'","\\\'","《","<","》",">"));
            }
            zyContent = zyContent.substring(ePoint+1);
            sPoint = zyContent.indexOf("<@");
            ePoint = zyContent.indexOf(">",sPoint);
        }
        
        //为了避免解析错误，字符串中的半角大于号、小于号都写成全角的
        //解析过程时，被改回半角的
        reSbf.append(BaseUtil.swapString(zyContent,"\"","\\\"","\'","\\\'","《","<","》",">"));
        //结尾不能放；符号，因为拼装的字符串有可能作为参数，放在语句之中
        //去掉了换行符，是因为sql语句中如果包含换行符，就报错
        return "\""+BaseUtil.swapString(reSbf.toString(),"\r","\\r","\n","\\n")+"\"";
    }
    
    /**
     * 处理SQL转义字符串 注意：在ScriptUtil类中，也有个类似的方法，要统一修改
     * 
     * @param zyContent 内容
     * @return 处理后的内容
     * @throws Exception 异常 2017年1月21日
     * @author MBG
     */
    private String fixSqlStr(String zyContent) throws Exception {
      // 语句段序列
      List<Object> sqlSubList = new ArrayList<Object>();
      // <@变量名 #注释>
      int sPoint = zyContent.indexOf("<@"); // 变量定义开始
      int ePoint = zyContent.indexOf("@>", sPoint); // 变量定义结束
      int tPoint; // 临时节点
      String fixSub; // 转译变量名
      String sqlVar; // 复杂语句段中的提交值
      String booVar; // 第三个参数 一段返回布尔型的表达式
      while (sPoint > -1 && ePoint > 0) {
        fixSub = zyContent.substring(sPoint + 2, ePoint);

        // 动态标识符为 <@"",ar,boolean@> 为复杂sql设置值
        tPoint = fixSub.indexOf("#");
        if (tPoint > -1) {
          fixSub = BaseUtil.trim(fixSub.substring(0, tPoint), " ", "\t", "\r", "\n");
        }
        if (sPoint > 0) {
          // 放入之前的语句段信息
          sqlSubList
              .add("\"" + BaseUtil.swapString(zyContent.substring(0, sPoint), "\"", "\\\"", "《", "<", "》", ">") + "\"");
        }
        // "",var,boolean
        fixSub = BaseUtil.trim(fixSub, " ", "\t", "\r", "\n");

        if (fixSub.startsWith("*")) {
          // <@*subSqlVar@> 其中 subSqlVar 是一个变量，其值是sql语句段
          sqlSubList.add(fixSub.substring(1));
        } else {
          tPoint = fixSub.lastIndexOf(","); // 语句段与传入参数的分隔符
          if (tPoint > 0) {
            sqlVar = BaseUtil.trim(fixSub.substring(tPoint + 1), " ", "\t", "\r", "\n");
            fixSub = BaseUtil.trim(fixSub.substring(0, tPoint), " ", "\t", "\r", "\n");

            // 检查是否有三个参数
            tPoint = fixSub.lastIndexOf(","); // 语句段与传入参数的分隔符
            if (tPoint > 0) {
              // 三个参数 :
              // 1: sql语句段（其中包含?问号,代表传入值 )
              // 2:传入值变量名 （如果该值为空或空字符串，并且第三个参数为false，则不使用该语句段）
              // 3：是否强制使用该语句段（如果该值为false，并且传入值为空，则不使用该语句段）
              booVar = sqlVar;
              sqlVar = BaseUtil.trim(fixSub.substring(tPoint + 1), " ", "\t","\r","\n");
              fixSub = BaseUtil.trim(fixSub.substring(0, tPoint), " ", "\t", "\r", "\n");
              sqlSubList.add(new String[] { fixSub, sqlVar, booVar });
            } else {
              // 传入了两个参数
              // 1：sql语句段（其中需要包含?符号，代表传入值）
              // 2：传入值变量名，如果该值为空（或空字符串）则不使用这个语句段
              sqlSubList.add(new String[] { fixSub, sqlVar });
            }
          } else {
            // 只传入了一个参数：传入值变量名，这种模式会在这处语句段自动增加?符号

            sqlSubList.add(new String[] { "\"?\"", fixSub, "1" });
          }
        }
        zyContent = zyContent.substring(ePoint + 2);
        sPoint = zyContent.indexOf("<@");
        ePoint = zyContent.indexOf("@>", sPoint);
      }
      if (zyContent.length() > 0) {
        sqlSubList.add("\"" + BaseUtil.swapString(zyContent, "\"", "\\\"", "《", "<", "》", ">") + "\"");
      }
      // 构建返回值
      StringBuffer reSbf = new StringBuffer();
      reSbf.append(" new Object[]{");
      boolean isFirst = true; // 是否为首次拼接
      for (Object ele : sqlSubList) {
        if (isFirst) {
          isFirst = false;
        } else {
          reSbf.append(",");
        }

        // 为了避免解析错误，字符串中的半角大于号、小于号都写成全角的 解析过程时，被改回半角的
        // 去掉了换行符，是因为sql语句中如果包含换行符就报错
        if (ele instanceof String) {
          // 常规Sql字符串
          reSbf.append(BaseUtil.swapString(((String) ele), "《", "<", "》", ">", "\r", "\\r", "\n", "\\n"));
        } else {
          // 为 String[]类型 0语句段 1提交参数值变量 2是否强制提交该值 （类型为整型或布尔型）
          reSbf.append("ScriptUtil.fixSqlSub(")
              // 在这里将双引号内部的全角逗号转换成了半角逗号
              .append(BaseUtil.swapString(((String[]) ele)[0], "，", ",", "《", "<", "》", ">", "\r", "\\r", "\n", "\\n"))
              .append(",").append(BaseUtil.swapString(((String[]) ele)[1], "，", ","));
          if (((String[]) ele).length > 2) {
            reSbf.append(",").append(BaseUtil.swapString(((String[]) ele)[2], "，", ",")).append(")");
          } else {
            reSbf.append(")");
          }
        }
      }
      // 结尾不能放；符号，因为拼装的字符串有可能作为参数，放在语句之中
      return reSbf.append("}").toString();
    }

    /**
     * 通过字段对象返回对应的java代码
     * 
     * @param sfVO 参数字段对象
     * @parm sVO 脚本信息容器
     * @return 对应的java代码 2014年7月25日
     * @author 马宝刚
     */
    private String getFieldContent(ScriptFieldVO sfVO, ScriptVO sVO) {
      // 构建返回值
      StringBuffer reSbf = new StringBuffer();
      if (sfVO.type == null || sfVO.type.length() < 1) {
        sVO.addErrorMsg("The Parameter Field Type is NULL Name:[" + sfVO.name + "]");
        log.warning("The Parameter Field Type is NULL Name:[" + sfVO.name + "]", null);
        return "";
      }
      if ("String".equals(sfVO.type)) {
        reSbf.append("		String ").append(sfVO.name).append(" = ").append("SString.valueOf(_in.get(\"")
            .append(sfVO.name).append("\"));\n");
      } else if ("int".equals(sfVO.type)) {
        reSbf.append("		int ").append(sfVO.name).append(" = ").append("SInteger.valueOf(_in.get(\"")
            .append(sfVO.name).append("\"));\n");
      } else if ("long".equals(sfVO.type)) {
        reSbf.append("		long ").append(sfVO.name).append(" = ").append("SLong.valueOf(_in.get(\"").append(sfVO.name)
            .append("\"));\n");
      } else if ("boolean".equals(sfVO.type)) {
        reSbf.append("		boolean ").append(sfVO.name).append(" = ").append("SBoolean.valueOf(_in.get(\"")
            .append(sfVO.name).append("\"));\n");
      } else if ("double".equals(sfVO.type)) {
        reSbf.append("		double ").append(sfVO.name).append(" = ").append("SDouble.valueOf(_in.get(\"")
            .append(sfVO.name).append("\"));\n");
      } else if ("float".equals(sfVO.type)) {
        reSbf.append("		float ").append(sfVO.name).append(" = ").append("SFloat.valueOf(_in.get(\"")
            .append(sfVO.name).append("\"));\n");
      } else if ("List".equals(sfVO.type)) {
        reSbf.append("		List ").append(sfVO.name).append(" = ").append("(List)_in.get(\"").append(sfVO.name)
            .append("\");\n");
      } else if ("Map".equals(sfVO.type)) {
        reSbf.append("		Map ").append(sfVO.name).append(" = ").append("(Map)_in.get(\"").append(sfVO.name)
            .append("\");\n");
      } else if (sfVO.type.startsWith("@")) {
        // 引用类主键
        Object paraObj = null;
        try {
          paraObj = getBeanFactory().getObject(sfVO.type.substring(1), this);
        } catch (Exception e) {
          e.printStackTrace();
          sVO.addErrorMsg("Load The Parameter Field Type:[" + sfVO.type + "] Name:[" + sfVO.name + "] Exception:"
              + DebugUtil.getExceptionInfo(e, "\n"));
          log.error("Not Find The Parameter Field Type:[\"+sfVO.type+\"] Name:[\"+sfVO.name+\"]", e);
          return "";
        }
        if (paraObj == null) {
          sVO.addErrorMsg("Not Find The Parameter Field Type:[" + sfVO.type + "] Name:[" + sfVO.name + "]");
          log.warning("Not Find The Parameter Field Type:[\"+sfVO.type+\"] Name:[\"+sfVO.name+\"]", null);
          return "";
        }
        reSbf.append("		" + paraObj.getClass().getName() + " ").append(sfVO.name).append(" = (")
            .append(paraObj.getClass().getName()).append(")_in.get(\"").append(sfVO.name).append("\");\n");
      } else {
        reSbf.append("		").append(sfVO.type).append(" ").append(sfVO.name).append(" = (").append(sfVO.type)
            .append(")_in.get(\"").append(sfVO.name).append("\");\n");
      }
      return reSbf.toString();
    }
    
    /**
     * 编译指定源文件
     * 
     * @param srcFilePath 源文件路径
     * @param sVO         脚本信息容器
     * @return 编译成功返回空，否则返回错误信息 2014年6月21日
     * @author 马宝刚
     */
    private String compile(String srcFilePath, ScriptVO sVO) {
      /*
       * 这个方法利用了 StandardJavaFileManager类的优点。这个文件管理器提供了一种方法来完成普通文件的输入输出工作。 同时在一个
       * DiagnosticListener实例的帮助下报告编译的诊断信息。后面将要用到的DiagnosticCollector类只是前面那个
       * listener的一个实现。 在确定什么东西是需要编译的之前，你需要一个文件管理器。创建一个文件管理器需要两个基本的步骤：
       * 创建一个DiagnosticCollector然后使用getStandardFileManager()方法向JavaCompiler申请文件管理器。 传递
       * DiagnosticListener实例作为getStandardFileManager()方法的参数。
       * 这个listener报告非致命性的错误，你也可以选择通过将它传递给getTask()方法与编译器共享这个listener
       * 
       * 2024-06-05 mbg 取消扩展库功能，改用：在WEB-INF/lib中创建子文件夹将jar包归类
       */
      //String classPath = calculateClassPath(sVO.extLibPaths);
      String classPath = calculateClassPath();
      ArrayList<String> options = new ArrayList<String>();
      options.add("-encoding");
      options.add("UTF-8");
      options.add("-nowarn");
      options.add("-classpath");
      options.add(classPath);
      options.add("-Xlint:unchecked");
      
      // 构建Java源文件实例
      List<ScriptFileObject> jfos = new ArrayList<>();
      jfos.add(sl.fm.getScriptSourceSfo(sVO));
		
      //重新构建报错提示，否则会显示老的报错信息，误导善良的程序员
      DiagnosticCollector<JavaFileObject> diagnostics = new DiagnosticCollector<JavaFileObject>();
      
      // 构建脚本编译文件管理类
      JavaCompiler.CompilationTask task = compiler.getTask(null, sl.fm, diagnostics, options, null, jfos);
      
      if (task.call()) {
        return null;
      }
      // 构建返回值
      StringBuffer reSbf = new StringBuffer();
      for (Diagnostic<?> diagnostic : diagnostics.getDiagnostics()) {
        if (!diagnostic.getKind().equals(Diagnostic.Kind.ERROR)) {
          continue;
        }
        reSbf.append("\n\nCode:").append(diagnostic.getCode()).append("\nKind:").append(diagnostic.getKind())
            .append("\nLineNumber:").append(diagnostic.getLineNumber()).append("\nColumnNumber:")
            .append(diagnostic.getColumnNumber()).append("\nPosition:").append(diagnostic.getPosition())
            .append("\nStart Position: ").append(diagnostic.getStartPosition()).append("\nEnd Position: ")
            .append(diagnostic.getEndPosition()).append("\nSource: ").append(diagnostic.getSource())
            .append("\nMessage:").append(diagnostic.getMessage(null)).append("\nClassPath:").append(classPath)
            // .append("\n\nSource:\n\n[[[[") //也不显示源码信息了，太大
            // .append(getSourceContent(diagnostic.getSource().toString()))
            // .append("]]]]\n");
            .append("\n");
      }
      try {
        sl.fm.close();
      } catch (IOException e) {
      }
      return reSbf.toString();
    }
    
    /**
     * 处理获取类路径
     * 
     * @author 刘虻 2009-11-18下午03:04:47
     * @param extLibPaths 扩展类路径
     * @return 类路径
     */
    // 取消扩展库功能，改用： 在WEB-INF/lib 中创建子文件夹将jar包归类
    //protected String calculateClassPath(String[] extLibPaths) {
    protected String calculateClassPath() {
      if (baseClassPath == null) {
        // scan cl chain to find
        StringBuffer classPath = new StringBuffer(SFilesUtil.fixPathByOs(_beanFactory.getWebInfPath(),"/classes"));
        // 搜索到的路径
        List<String> pathList = _beanFactory.getClassPathList();
        for (String path : pathList) {
          classPath.append(File.pathSeparatorChar).append(path);
        }
        // 获取项目中的包根路径
        String libPath = _beanFactory.getWebInfPath() + "/lib";
        // 搜索到的路径
        pathList = new ArrayList<String>();
        try {
          // 执行搜索项目库文件夹
          SFilesUtil.getFileList(pathList, libPath, null, "jar;zip", true, false);
        } catch (Exception e) {
          e.printStackTrace();
        }
        for (String path : pathList) {
          classPath.append(File.pathSeparatorChar).append(path);
        }
        baseClassPath = classPath.toString();
      }
      /*
      // 搜索扩展包子路径序列
      List<String> classPathList = new ArrayList<String>();
      // 执行获取类路径
      calculateClassPath(extLibPaths, classPathList);
      StringBuffer classPath = new StringBuffer(); // 类路径
      for (String extPath : classPathList) {
        classPath.append(File.pathSeparatorChar).append(extPath);
      }
      if (classPath.length() > 0) {
        return baseClassPath + classPath;
      }
      */
      return baseClassPath;
    }
    
    /**
     * 处理获取类路径
     * 
     * 2024-06-05 mbg 取消扩展库功能，改用：在WEB-INF/lib中创建子文件夹将jar包归类
     * 
     * @author 刘虻 2009-11-18下午03:04:47
     * @param extLibPaths 扩展类路径
     * @param libPathList 需要加载的包绝对路径序列
     * @return 类路径
     */
    /*
    private void calculateClassPath(String[] extLibPaths, List<String> libPathList) {
      if (extLibPaths != null && extLibPaths.length > 0) {
        String basePath = infPath("/ext_lib"); // 扩展包根路径
        String path; // 包路径
        for (int i = 0; i < extLibPaths.length; i++) {
          path = extLibPaths[i];
          if (path == null) {
            continue;
          }
          path = path.trim();
          if (path.length() < 1) {
            continue;
          }
          path = SFilesUtil.getAllFilePath(path, basePath);
          calculateClassPath(path, basePath, libPathList);
        }
      }
    }
    */
	
	
    /**
     * 处理获取类路径
     * 
     * 2024-06-05 mbg 取消扩展库功能，改用：在WEB-INF/lib中创建子文件夹将jar包归类
     * 
     * @author 刘虻 2009-11-18下午03:04:47
     * @param extLibAllPath 扩展类绝对路径（肯定不为空）
     * @param extBasePath   扩展库根路径
     * @param libPathList   需要加载的包绝对路径序列
     * @return 类路径
     */
    /*
    private void calculateClassPath(String extLibAllPath, String extBasePath, List<String> libPathList) {
      // 检查扩展库中是否存在readme.txt文件（注意：文件名全部小写）
      File readmeFile = new File(extLibAllPath + "/readme.txt");
      if (readmeFile.exists()) {
        List<String> cntList = null; // 文件内容
        String[] paths = null; // 用分隔符分隔为多个子路径
        String objPath; // 需要搜索的路径
        try {
          // 获取文件内容
          cntList = BaseUtil.splitToList(FileCopyTools.copyToString(readmeFile, "UTF-8"), "\r\n", "\n");
        } catch (Exception e) {
          e.printStackTrace();
          return;
        }
        if (cntList == null || cntList.size() < 1) {
          return;
        }
        for (String ele : cntList) {
          ele = ele.trim();
          if (ele.startsWith("#") || ele.length() < 1) {
            continue;
          }
          paths = BaseUtil.split(ele, ":", "：", ";", "；", ",", "，");
          for (int i = 0; i < paths.length; i++) {
            if (paths[i].length() < 1) {
              continue;
            }
            objPath = SFilesUtil.getAllFilePath(paths[i], extBasePath);

            // 加载引用外部的扩展包
            calculateClassPath(objPath, extBasePath, libPathList);
          }
        }
      }
      // 如果该脚本引用了扩展包（指定了扩展包相对文件夹路径，相对 WEB-INF/ext_lib 文件夹）
      List<String> pathList = new ArrayList<String>(); // 搜索到的路径
      try {
        // 执行搜索项目库文件夹
        SFilesUtil.getFileList(pathList, extLibAllPath, null, "jar;zip", true, false);
        for (String path : pathList) {
          if (!libPathList.contains(path)) {
            libPathList.add(path);
          }
        }
      } catch (Exception e) {
        e.printStackTrace();
      }
    }
	*/
	
    /**
     * url转文件路径
     * 
     * @author 刘虻 2009-11-18下午03:04:06
     * @param url url路径
     * @return 文件路径
     */
    @SuppressWarnings("deprecation")
    protected final String toFile(URL url) {
      if (url.getProtocol().indexOf("file") < 0) {
        return null;
      }
      String result = url.getPath();
      if (result.charAt(0) == '/' && File.separatorChar == '\\') {
        result = result.substring(1);
      }
      return URLDecoder.decode(result);
    }
  }
